package com.ss.demo.springbootdemo.ioc.context;

import com.ss.demo.springbootdemo.ioc.annotation.MyAutowired;
import com.ss.demo.springbootdemo.ioc.annotation.MyComponent;
import com.ss.demo.springbootdemo.ioc.annotation.MyQualifier;
import com.ss.demo.springbootdemo.ioc.annotation.MyValue;
import com.ss.demo.springbootdemo.ioc.definition.BeanDefinition;
import com.ss.demo.springbootdemo.ioc.util.MyTools;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 作者: MrZhangJR
 * 时间: 2021/11/11 17:16
 * 描述:
 */
public class MyAnnotationApplicationContext implements MyApplicationContext{

    private Map<String, Object> iocContainer = new HashMap<>();

    public MyAnnotationApplicationContext(String packageName) {
        // 1. 扫描组件, 输入包名; 输出BeanDefinitions,封装了beanName以及clazz
        List<Class<?>> clazzList = MyTools.getClasses(packageName);
        Set<BeanDefinition> beanDefinitionSet = getBeanDefinitions(clazzList);
        // 2. 实例化+初始化  输入: BeanDefinitions
        createObject(beanDefinitionSet);
        // 3. 依赖注入
        dependencyInjection(beanDefinitionSet);
    }

    private void dependencyInjection(Set<BeanDefinition> beanDefinitionSet) {
        for (BeanDefinition beanDefinition : beanDefinitionSet) {
            Class clazz = beanDefinition.getClazz();
            String beanName = beanDefinition.getBeanName();
            for (Field declaredField : clazz.getDeclaredFields()) {
                MyAutowired myAutowired  = declaredField.getAnnotation(MyAutowired.class);
                if (myAutowired != null) {
                    MyQualifier myQualifier  = declaredField.getAnnotation(MyQualifier.class);
                    if (myQualifier != null) {
                         // byName
                        Object objParam = getBean(myQualifier.value());
                        String methodName = getMethodName(declaredField);
                        try {
                            Method method = clazz.getMethod(methodName, declaredField.getType());
                            // 当前对象, 要注入的对象
                            method.invoke(getBean(beanName),objParam);
                        } catch (NoSuchMethodException e) {
                            e.printStackTrace();
                        } catch (InvocationTargetException e) {
                            e.printStackTrace();
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    } else {
                        // byType
                        Object objParam = null;
                        // 获取到当前属性的类型, 去ioc中寻找对应的bean
                        String typeClassName = declaredField.getType().getName();
                        for (Map.Entry<String, Object> entry : iocContainer.entrySet()) {
                            Object value = entry.getValue();
                            String key = entry.getKey();
                            if (value.getClass().getName().equals(typeClassName)) {
                                try {
                                    String methodName = getMethodName(declaredField);
                                    Method method = clazz.getMethod(methodName, declaredField.getType());
                                    // 当前对象, 要注入的对象
                                    method.invoke(getBean(beanName),getBean(key));
                                } catch (NoSuchMethodException e) {
                                    e.printStackTrace();
                                } catch (InvocationTargetException e) {
                                    e.printStackTrace();
                                } catch (IllegalAccessException e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                    }
                }
            }
        }
    }


    private void createObject(Set<BeanDefinition> beanDefinitionSet) {
        for (BeanDefinition beanDefinition : beanDefinitionSet) {
            Class clazz = beanDefinition.getClazz();
            String beanName = beanDefinition.getBeanName();
            try {
                Object o = clazz.newInstance();
                iocContainer.put(beanName, o);
                initValue(o,clazz,beanName);
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }

    private void initValue(Object o, Class clazz, String beanName) {
        for (Field declaredField : clazz.getDeclaredFields()) {
            MyValue myValue = declaredField.getAnnotation(MyValue.class);
            if (myValue != null) {
                try {
                    String methodName = getMethodName(declaredField);
                    Method declaredMethod = clazz.getDeclaredMethod(methodName, declaredField.getType());
                    String fieldTypeName = declaredField.getType().getName();
                    Object value = null;
                    switch (fieldTypeName) {
                        case "java.lang.Integer":
                            value = Integer.parseInt(myValue.value());
                            break;
                        case "java.lang.String":
                            value = myValue.value();
                            break;
                        case "java.lang.Double":
                            value = Double.parseDouble(myValue.value());
                            break;
                    }
                    declaredMethod.invoke(o, value);
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    private String getMethodName(Field declaredField) {
        return "set" + declaredField.getName().substring(0, 1).toUpperCase(Locale.ROOT) + declaredField.getName().substring(1);
    }

    private Set<BeanDefinition> getBeanDefinitions(List<Class<?>> clazzList) {
        Set<BeanDefinition> beanDefinitionSet = new HashSet<>();
        for (Class<?> clazz : clazzList) {
            // 看是否有Component注解
            MyComponent myComponent = clazz.getAnnotation(MyComponent.class);
            if (myComponent != null) {
                String beanName = getBeanName(clazz.getPackage().getName(), clazz.getName(), myComponent);
                BeanDefinition beanDefinition = new BeanDefinition(beanName, clazz);
                beanDefinitionSet.add(beanDefinition);
            }
        }
        return beanDefinitionSet;
    }

    private String getBeanName(String packageName, String className, MyComponent myComponent) {
        String beanName = myComponent.value();
        if ("".equals(beanName)) {
            beanName = className.replace(packageName + ".", "");
            beanName = beanName.substring(0,1).toLowerCase() + beanName.substring(1);
        }
        return beanName;
    }

    @Override
    public Object getBean(String beanName) {
        return iocContainer.get(beanName);
    }
}
